import javax.swing.*;
import java.awt.*;
import java.io.*;
import java.lang.reflect.Array;
import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.util.Random;
import java.awt.Button;
import java.awt.Color;
import java.awt.FlowLayout;
import java.awt.Frame;
/*
    选择两个大素数 p 和 q
    计算乘积 n = p * q 和 欧拉函数 Fn = (p - 1) * (q - 1)
    选择大于 1 小于 Fn 的随机整数 e，使得 gcd(e, Fn) = 1
    计算 d 使得 d * e = 1 mod Fn
    公钥 PK= {e, n}, 私钥SK={d}
    加密变换 C = M ^ e % n（C 为加密得到的密文，M 为明文）
    解密变换 M = C ^ d % n
*/
public class RSA {
    private final static int numLength = 1024;//素数长度
    private final static int accuracy = 100; //素数的准确率
    //密匙对
    static class SecretKey {
        BigInteger n, e, d;
        public SecretKey(BigInteger n, BigInteger e, BigInteger d) {
            this.n = n;
            this.e = e;
            this.d = d;
        }
        public PublicKey getPublicKey() {
            return new PublicKey(n, e);
        }
        public PrivateKey getPrivateKey() {
            return new PrivateKey(n, d);
        }
        //私钥
        static class PrivateKey {
            public BigInteger n, d;

            public PrivateKey(BigInteger n, BigInteger d) {
                this.n = n;
                this.d = d;
            }
        }
        //公钥
        static class PublicKey {
            public BigInteger n, e;

            public PublicKey(BigInteger n, BigInteger e) {
                this.n = n;
                this.e = e;
            }
        }
    }
    //获取最大公约数
    private BigInteger getGCD(BigInteger a, BigInteger b) {
        if (b.byteValue() == 0) {
            return a;
        }
        return getGCD(b, a.mod(b));
    }
    //扩展欧几里得方法
    private static BigInteger[] extGCD(BigInteger a, BigInteger b) {
        if (b.signum() == 0) {
            return new BigInteger[]{a, BigInteger.ONE, BigInteger.ZERO};
        } else {
            BigInteger[] BigIntegers = extGCD(b, a.mod(b));
            BigInteger y = BigIntegers[1].subtract(a.divide(b).multiply(BigIntegers[2]));
            return new BigInteger[]{BigIntegers[0], BigIntegers[2], y};
        }
    }
    //超大整数超大次幂然后对超大的整数取模，利用蒙哥马利乘模算法,
    //(base ^ exp) mod n
    //依据(a * b) mod n=(a % n)*(b % n) mod n
    private static BigInteger expMode(BigInteger base, BigInteger exp, BigInteger mod) {
        if (mod.equals(BigInteger.ONE)) {
            return BigInteger.ZERO;
        }
        BigInteger result = BigInteger.ONE;
        base = base.mod(mod);
        while (exp.compareTo(BigInteger.ZERO) > 0) {
            if (exp.and(BigInteger.ONE).equals(BigInteger.ONE)) {
                result = result.multiply(base).mod(mod);
            }
            exp = exp.shiftRight(1);
            base = base.multiply(base).mod(mod);
        }
        return result;
        /*
            D=1
            WHILE E>=0
        　　  IF E%2=0
        　　      C=C*C % N
        　　  E=E/2
        　　ELSE
        　　  D=D*C % N
        　　  E=E-1
        　　RETURN D
        */
    }
    //产生两个随机1024位大质数
    public static BigInteger[] getRandomPQ() {
        BigInteger p = BigInteger.probablePrime(numLength, new Random());
        while (!p.isProbablePrime(accuracy)) {
            p = BigInteger.probablePrime(numLength, new Random());
        }
        BigInteger q = BigInteger.probablePrime(numLength, new Random());
        while (!q.isProbablePrime(accuracy)) {
            q = BigInteger.probablePrime(numLength, new Random());
        }
        return new BigInteger[]{p, q};
    }
    //产生公钥与私钥
    public static SecretKey generateKey(BigInteger p, BigInteger q) {
        //令n = p * q。取 φ(n) = (p-1) * (q-1)。
        BigInteger n = p.multiply(q);
        //计算与n互质的整数个数 欧拉函数
        BigInteger fy = p.subtract(BigInteger.ONE).multiply(q.subtract(BigInteger.ONE));
        //取 e ∈ [1 < e < φ(n) ] ，( n , e )作为公钥对，通常取65537
        BigInteger e = new BigInteger("65537");
        //计算ed与fy的模反元素d。令 ed mod φ(n)  = 1，计算d，然后将( n , d ) 作为私钥对
        BigInteger[] BigIntegers = extGCD(e, fy);
        //计算出的x不能是负数，如果是负数，则进行x=x+fy。使x为正数，但是x < fy。
        BigInteger x = BigIntegers[1];
        if (x.signum() == -1) {
            x = x.add(fy);
        }
        System.out.println("n：  " + n);
        System.out.println("e：  " + e);
        System.out.println("d：  " + x);
        System.out.println("公钥对：{" + n + "," + e + "}");
        System.out.println("私钥对：{" + n + "," + x + "}");
        //返回计算出的密钥
        return new SecretKey(n, e, x);
    }
    public static SecretKey generateKey() {
        BigInteger[] pq = getRandomPQ();
        return generateKey(pq[0], pq[1]);
    }
    //加密
    public static BigInteger encrypt(BigInteger text, SecretKey.PublicKey publicKey) {
        return expMode(text, publicKey.e, publicKey.n);
    }
    //解密
    public static BigInteger decrypt(BigInteger cipher, SecretKey.PrivateKey privateKey) {
        return expMode(cipher, privateKey.d, privateKey.n);
    }
    public static byte[] toByteArray8(BigInteger BigInteger) {
        byte[] bytes = BigInteger.toByteArray();
        if (bytes[0] == 0) {
            byte[] tmp = new byte[bytes.length - 1];
            System.arraycopy(bytes, 1, tmp, 0, tmp.length);
            bytes = tmp;
        }
        if (bytes.length < 8) {
            byte[] tmp = new byte[8];
            System.arraycopy(bytes, 0, tmp, 8 - bytes.length, bytes.length);
            bytes = tmp;
        }
        return bytes;
    }
    public static byte[] toByteArray16(BigInteger BigInteger) {
        byte[] bytes = BigInteger.toByteArray();
        if (bytes[0] == 0) {
            byte[] tmp = new byte[bytes.length - 1];
            System.arraycopy(bytes, 1, tmp, 0, tmp.length);
            bytes = tmp;
        }
//           假如转换的byte数组少于16位，则在前面补齐0
        if (bytes.length < 16) {
            byte[] tmp = new byte[16];
            System.arraycopy(bytes, 0, tmp, 16 - bytes.length, bytes.length);
            bytes = tmp;
        }
        return bytes;
    }
    public static byte[] toByteArray256(BigInteger BigInteger) {
        byte[] bytes = BigInteger.toByteArray();
        if (bytes[0] == 0) {
            byte[] tmp = new byte[bytes.length - 1];
            System.arraycopy(bytes, 1, tmp, 0, tmp.length);
            bytes = tmp;
        }
//           假如转换的byte数组少于16位，则在前面补齐0
        if (bytes.length < 256) {
            byte[] tmp = new byte[256];
            System.arraycopy(bytes, 0, tmp, 256 - bytes.length, bytes.length);
            bytes = tmp;
        }
        return bytes;
    }
    /*   字节转换为二进制
     *   基于存储形式进行转换  */
    public static StringBuffer byteTOBit(byte s) {
//        StringBuffer res = new StringBuffer(Integer.toBinaryString(s));
//        while (res.length() % 8 != 0) {
//            res.insert(0, 0);
//        }
//        return res;
        StringBuffer res = new StringBuffer();
        for (int cnt = 0; cnt < 8; cnt++) {
            res.insert(0, (s >> cnt) & 1);
//            res.append((s >> cnt) & 1);
        }
        return res;
    }
    /*  二进制转换为字节    */
    public static byte bitTOByte(StringBuffer s) {
        int t = 0;
        for (int i = 0; i < s.length() / 8; i++) {
            t = Integer.parseInt(s.substring(i * 8, (i + 1) * 8), 2);

        }
        return (byte) t;
    }
    /*  将长度为8字节的字符串转换为二进制   */
    public static StringBuffer byte8TOBit64(byte[] bytes) {
        StringBuffer res = new StringBuffer();
        for (int cnt = 0; cnt < 8; cnt++) {
            res.append(byteTOBit(bytes[cnt]));
        }
        return res;
    }
    /*  将二进制位串转为字节长度为8的字符串    */
    public static byte[] bit64TOByte8(StringBuffer s) {
//        StringBuffer res = new StringBuffer();
        byte[] res = new byte[8];
        for (int cnt = 0; cnt < 8; cnt++) {
            res[cnt] = bitTOByte(new StringBuffer(s.substring(cnt * 8, cnt * 8 + 8)));
        }
        return res;
    }
    public static byte[] bit128TOByte16(StringBuffer s) {
//        StringBuffer res = new StringBuffer();
        byte[] res = new byte[16];
        for (int cnt = 0; cnt < 16; cnt++) {
            res[cnt] = bitTOByte(new StringBuffer(s.substring(cnt * 8, cnt * 8 + 8)));
        }
        return res;
    }
    public static StringBuffer byte16TOBit128(byte[] bytes) {
        StringBuffer res = new StringBuffer();
        for (int cnt = 0; cnt < 16; cnt++) {
            res.append(byteTOBit(bytes[cnt]));
        }
        return res;
    }
    /*  对文件流进行读取与写入
     *   实现文件的加密与解密  */
    public static void Encrypt(String inputFileName, String outputFileName, SecretKey.PublicKey publicKey) throws Exception {
        try {
            //传入文件路径fileName
            File inputfile = new File(inputFileName);
            System.out.println("加密总字节" + inputfile.length());
            if (!inputfile.exists()) {
                throw new Exception("该文件不存在");
            }
            File outfile = new File(outputFileName);
            if (!outfile.exists()) {
                outfile.createNewFile();
            }
            FileInputStream in = new FileInputStream(inputFileName);
            FileOutputStream out = new FileOutputStream(outputFileName);
            //每次读8个字节，放到数组里
            byte[] bytes = new byte[8];
            int count = 0;// 记录每次读取的字节的个数以及是否读取完
            int infilecount = 0;
            int oufilecount = 0;

            while (count != -1) {
                count = in.read(bytes);
                if (count == -1) {
                    continue;
                }
                infilecount += count;
                System.out.println("加密进度" + infilecount + "/" + inputfile.length());
                System.out.println("明文： " + byte8TOBit64(bytes));
                if (count != 8) {
                    for (int i = count; i < 7; i++) {
                        bytes[i] = '\0';
                    }
                    bytes[7] = (byte) (8 - count);
                }
                BigInteger cipherString = encrypt(new BigInteger(1, bytes), publicKey);
                /*if(toByteArray2(cipherString).length != 16){
                    System.out.println(toByteArray2(cipherString).length);
                    break;
                }*/
                System.out.println("cipherString        " + cipherString);
                out.write(toByteArray256(cipherString));
            }
            in.close();
            out.close();
            System.out.println("加密总字节" + inputfile.length());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    public static void Decrypt(String inputFileName, String outputFileName, SecretKey.PrivateKey privateKey) throws Exception {
        try {
            //传入文件路径fileName
            File inputfile = new File(inputFileName);
            System.out.println("解密总字节" + inputfile.length());
            if (!inputfile.exists()) {
                throw new Exception("该文件不存在");
            }
            long fileLong = inputfile.length(); // 记录文件字节长度，方便判断最后一个字节的填充

            File outfile = new File(outputFileName);
            if (!outfile.exists()) {
                outfile.createNewFile();
            }
            FileInputStream in = new FileInputStream(inputFileName);
            FileOutputStream out = new FileOutputStream(outputFileName);
            //每次读256个字节，放到数组里
            byte[] bytes = new byte[256];

            int count = 0;// 记录每次读取的字节的个数以及是否读取完
            while (in.read(bytes) != -1) {
                count += bytes.length;
                System.out.println(count);
                System.out.println("解密进度" + count + "/" + inputfile.length());
                System.out.println("加密文件：" + (bytes));

                BigInteger plainString = decrypt(new BigInteger(1, bytes), privateKey);
                byte[] bytes2 = toByteArray8(plainString);
                if (count == fileLong && (int) bytes2[7] < 8) {
                    for (int i = 0; i < 8 - (int) bytes2[7]; i++) {
                        out.write(bytes2[i]);
                    }
                } else {
                    out.write(bytes2);
                }
//                out.write(bytes2);
            }
            in.close();
            out.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    public static boolean compareFiles(File file1, File file2) throws IOException {
        if (!file1.exists() || !file2.exists()) {
            throw new FileNotFoundException("One or both files do not exist.");
        }

        if (file1.isDirectory() || file2.isDirectory()) {
            throw new IllegalArgumentException("Cannot compare directories.");
        }

        if (file1.length() != file2.length()) {
            System.out.println(file1.length());
            System.out.println(file2.length());
            return false;
        }
        try (InputStream inputStream1 = new FileInputStream(file1);
             InputStream inputStream2 = new FileInputStream(file2)) {
            byte[] buffer1 = new byte[8192];
            byte[] buffer2 = new byte[8192];
            int numRead1, numRead2;

            do {
                numRead1 = inputStream1.read(buffer1);
                numRead2 = inputStream2.read(buffer2);
                if (numRead1 != numRead2) {
                    System.out.println(numRead1);
                    System.out.println(numRead2);
                    return false;
                }
                if (numRead1 > 0 && !java.util.Arrays.equals(buffer1, buffer2)) {
                    System.out.println(buffer1);
                    System.out.println(buffer2);
                    return false;
                }
            } while (numRead1 > 0);
        }

        return true;
    }
    public static void test(String inputFileName, String outputFileName) throws Exception {
        SecretKey secretKey = RSA.generateKey();
        //明文内容不要超过1024位,超过后需要分段加密
        System.out.println("公钥" + secretKey.getPublicKey());
        System.out.println("私钥" + secretKey.getPrivateKey());
        File inputfile = new File(inputFileName);

//        System.out.println("解密总字节" + inputfile.length());
        if (!inputfile.exists()) {
            throw new Exception("该文件不存在");
        }
        long fileLong = inputfile.length(); // 记录文件字节长度，方便判断最后一个字节的填充

        File outfile = new File(outputFileName);
        if (!outfile.exists()) {
            outfile.createNewFile();
        }
        FileInputStream in = new FileInputStream(inputFileName);
        FileOutputStream out = new FileOutputStream(outputFileName);
        //每次读256个字节，放到数组里
        byte[] bytes = new byte[8];
        int count = 0;// 记录每次读取的字节的个数以及是否读取完
        int count2 = 0;
        int text = 0;
        int plaintext = 0;
        while (count != -1) {
            count = in.read(bytes);
            text += count;
            count2 += bytes.length;
            if (count == -1) {
                continue;
            }
            if (count != 8) {
                for (int i = count; i < 7; i++) {
                    bytes[i] = '\0';
                }
                bytes[7] = (byte) (8 - count);
            }
            System.out.println(text);
            System.out.println("加密前：    " + new BigInteger(1, bytes));
            BigInteger cipherString = encrypt(new BigInteger(1, bytes), secretKey.getPublicKey());
            System.out.println("加密后：    " + cipherString);
            byte[] bytes3 = toByteArray256(cipherString);
            System.out.println("解密前：    " + new BigInteger(1, bytes3));
            BigInteger plainString = decrypt(new BigInteger(1, bytes3), secretKey.getPrivateKey());
            System.out.println("解密后：    " + plainString);
            plaintext += toByteArray8(plainString).length;
            System.out.println(plaintext);
            System.out.println();
            byte[] bytes2 = toByteArray8(plainString);
//            if (count2 >= fileLong && (int) bytes2[7] < 8) {
//                for (int i = 0; i < 8 - (int) bytes2[7]; i++) {
//                    out.write(bytes2[i]);
//                }
//            } else {
//                out.write(bytes2);
//            }
            out.write(bytes2);
        }
        in.close();
        out.close();
        System.out.println("加密总字节" + inputfile.length());
    }
    public static void main(String[] args) throws Exception {
        long stime = System.currentTimeMillis();
        SecretKey secretKey = RSA.generateKey();
//        //明文内容不要超过1024位,超过后需要分段加密
        System.out.println("公钥" + secretKey.getPublicKey());
        System.out.println("私钥" + secretKey.getPrivateKey());
        RSA.Encrypt("1.zip","加密.txt", secretKey.getPublicKey());
        RSA.Decrypt("加密.txt","解密.zip", secretKey.getPrivateKey());
//        File file1 = new File("1.jpg");
//        File file2 = new File("解密.jpg");
//        System.out.println(compareFiles(file1,file2));
//        // 结束时间
        long etime = System.currentTimeMillis();
        // 计算执行时间
        System.out.printf("执行时长：%d 毫秒.", (etime - stime));

    }
}






